package org.javanum.number;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;

import java.math.BigDecimal;
import java.math.MathContext;
import java.util.Arrays;
import java.util.Collection;

import static junit.framework.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.junit.Assume.assumeNotNull;
import static org.junit.Assume.assumeTrue;

/**
 * User: Scott
 * Date: Dec 5, 2009
 * Time: 10:11:42 PM
 */
@RunWith(Parameterized.class)
public class BigComplexTest {
    private final BigComplex firstNum;
    private final BigComplex secondNum;

    private static final MathContext deltaContext = new MathContext(10);
    private static final BigDecimal deltaTolerance;
    static{
        BigDecimal temp = new BigDecimal("1");
        deltaTolerance=temp.movePointLeft(deltaContext.getPrecision());
    }

    public BigComplexTest(BigComplex firstNum, BigComplex secondNum) {
        this.firstNum = firstNum;
        this.secondNum = secondNum;
    }

    @Parameterized.Parameters
    public static Collection<Object[]>getParameters(){
        return Arrays.asList(
                new Object[][]{
                        {
                         BigComplex.valueOf(
                                 new BigDecimal("1.0"),
                                 new BigDecimal("2.0")),
                         BigComplex.valueOf(
                                 new BigDecimal("3000.4"),
                                 new BigDecimal("2500.90101912"))
                        }
                });
    }
    
    @SuppressWarnings({"ObjectEqualsNull"})
    @Test
    public void testEquals() throws Exception{
        BigComplex x = getOneNumber();
        assumeNotNull(x);
        assertFalse("Nullity fails:x.equals(null)=true;x="+x,
                    x.equals(null));
        assertTrue("Reflexivity fails: x!=itself; x="+x,
                    x.equals(x));

        BigComplex y = BigComplex.valueOf(x.real(),x.imaginary());
        assertTrue("Reflexivity fails: x!=y, " +
                "but y is constructed from x;" +
                "x="+x+", y="+y,x.equals(y));
        assertTrue("Symmetry fails: x.equals(y)=true," +
                " but y.equals(x)=false; x="+x+", y="+y,y.equals(x));

        BigComplex z = BigComplex.valueOf(x.real(),x.imaginary());
        assertTrue("Transitivity fails: " +
                "y.equals(z)=false;y="+y+", z="+z,y.equals(z));
        assertTrue("Transitivity fails: "+
                "x.equals(y) and y.equals(z), " +
                "but x.equals(z)=false;x="+x+", y="+y+", z="+z,
                x.equals(z));
    }

    @Test
    public void testNonEquals() throws Exception{
        BigComplex x = firstNum;
        BigComplex y = secondNum;
        assumeNotNull(x);
        assumeNotNull(y);
        assumeTrue(!x.realPart().equals(y.realPart()));
        assumeTrue(!x.imaginaryPart().equals(y.imaginaryPart()));

        assertFalse("Non equal elements evaluate equals() to true;" +
                " x="+x+",y="+y,x.equals(y));
    }

    private BigComplex getOneNumber(){
        return firstNum !=null?firstNum:secondNum;
    }

    @Test
    public void testEqualElementsEqualHashCode() throws Exception{
        BigComplex x = getOneNumber();
        assumeNotNull(x);
        BigComplex y = BigComplex.valueOf(x.real(),x.imaginary());
        assumeTrue(x.equals(y));

        assertEquals("Equal elements hash to different values; " +
                "x="+" ,y="+y,x.hashCode(),y.hashCode());

    }

    @Test
    public void testUnequalElementsUnequalHashCode() throws Exception{
        BigComplex x = firstNum;
        BigComplex y = secondNum;
        assumeNotNull(x);
        assumeNotNull(y);
        assumeTrue(!(x.equals(y)));

        assertTrue("Unequal elements hash to the same value; " +
                "x="+x+", y="+y,x.hashCode()!=y.hashCode());
    }
    @Test

    public void testConjugate() throws Exception {
        BigComplex x = getOneNumber();
        assumeNotNull(x);

        BigComplex conjugate = x.conjugate();

        assertEquals("Conjugate real part does not match",
                x.real(),conjugate.real());

        assertEquals("Conjugate imaginary part does not " +
                "match its negative",
                x.imaginary().negate(),
                conjugate.imaginary());
    }

    @Test
    public void testModulus() throws Exception {
        BigComplex x = getOneNumber();
        assumeNotNull(x);

        BigDecimal modulus = x.modulus(new MathContext(20));
        BigDecimal real = x.real();
        BigDecimal imag = x.imaginary();
        BigDecimal realMod = BigMath.bigRoot(
                real.pow(2).add(imag.pow(2)),deltaContext);

        assertTrue("Modulus fails. expected="+modulus+", actual="+
                realMod,
                modulus.subtract(realMod).abs().
                        compareTo(deltaTolerance)<0);
    }

    @Test
    public void testDivideNonZero() throws Exception {
        BigComplex x = firstNum;
        BigComplex y = secondNum;
        assumeNotNull(x,y);
        assumeTrue(!(y.real().equals(BigDecimal.ZERO)&&
                     y.imaginary().equals(BigDecimal.ZERO)));

        MathContext divideAccuracy = new MathContext(20);
        BigComplex z = x.divide(y,divideAccuracy);

        BigComplex retValue = z.multiply(y);

        assertTrue(String.format("Dividing, then multiplying does " +
                "not result in the same number. Expected real: %1$s. " +
                "Actual real:%2$s",x.real(),retValue.real()),
                BigMath.equalToWithinPrecision(x.real(),retValue.real(),
                        divideAccuracy.getPrecision()));

        assertTrue(String.format("Dividing, then multiplying does " +
                "not result in the same number. Expected imaginary: %1$s. "+
                "Actual imaginary:%2$s",x.imaginary(),retValue.imaginary()),
                BigMath.equalToWithinPrecision(x.imaginary(),retValue.imaginary(),
                        divideAccuracy.getPrecision()));
    }

    @SuppressWarnings({"UnusedDeclaration"})
    @Test(expected=ArithmeticException.class)
    public void testDivideByZeroThrowsException() throws Exception {
        BigComplex x = firstNum;
        assumeNotNull(x);

        BigComplex result = x.divide(BigComplex.ZERO);
    }

    @Test
    public void testMultiply() throws Exception {
        BigComplex x = firstNum;
        BigComplex y = secondNum;
        assumeNotNull(x,y);

        BigComplex z = x.multiply(y);

        BigDecimal resultReal =
                x.real().multiply(y.real()).subtract(
                            x.imaginary().multiply(y.imaginary()));

        assertTrue("Multiply fails: expected real="
                +resultReal+", actual real="+z.real(),
                resultReal.subtract(z.real()).abs().
                        compareTo(deltaTolerance)<0);

        BigDecimal resultImaginary =
                x.real().multiply(y.imaginary()).add(
                        x.imaginary().multiply(y.real()));
        assertTrue("Multiply fails: expected imaginary="
                +resultImaginary+", actual imaginary="+z.imaginary(),
                resultImaginary.subtract(z.imaginary()).abs().
                        compareTo(deltaTolerance)<0);
    }

    @Test
    public void testAdd() throws Exception {
        BigComplex x = firstNum;
        BigComplex y = secondNum;
        assumeNotNull(x,y);

        BigComplex z = x.add(y);

        BigDecimal addReal = x.real().add(y.real());
        assertTrue("Addition fails: expected real="+addReal+
                ", actual real="+z.real(),
                addReal.subtract(z.real()).abs().
                        compareTo(deltaTolerance)<0);

        BigDecimal addImaginary = x.imaginary().add(y.imaginary());
        assertTrue("Addition fails: expected imaginary="+addImaginary+
                        ", actual imaginary="+z.imaginary(),
                        addImaginary.subtract(z.imaginary()).abs().
                                compareTo(deltaTolerance)<0);
    }

    @Test
    public void testAdditiveInverse() throws Exception {
        BigComplex x = getOneNumber();
        assumeNotNull(x);

        BigComplex inverse = x.additiveInverse();

        BigComplex testZero = x.add(inverse);

        assertTrue("Real part is not zero",
                BigDecimal.ZERO.compareTo(testZero.real())==0);
        assertTrue("Imaginary part is not zero",
                BigDecimal.ZERO.compareTo(testZero.real())==0);
    }

    @Test
    public void testSubtract() throws Exception {
        BigComplex x = firstNum;
        BigComplex y = secondNum;
        assumeNotNull(x,y);

        BigComplex z = x.subtract(y);

        BigDecimal correctReal = x.real().subtract(y.real());
        assertTrue("Subtraction fails: expected real="
                +correctReal+", actual real="+z.real(),
                correctReal.subtract(z.real()).abs().
                        compareTo(deltaTolerance)<0);
        BigDecimal correctImaginary = x.imaginary().subtract(y.imaginary());
        assertTrue("Subtraction fails: expected imaginary="
                +correctImaginary+", actual imaginary="+z.imaginary(),
                correctImaginary.subtract(z.imaginary()).abs().
                        compareTo(deltaTolerance)<0);
    }

    @Test
    public void testMultiplicativeInverse() throws Exception {
        BigComplex x = getOneNumber();
        assumeNotNull(x);

        BigComplex inverse = x.multiplicativeInverse();

        BigComplex z = x.multiply(inverse);
        assertTrue(String.format("Multiplicative Inverse fails: " +
                "expected real=%1$gg, actual real=%2$g",
                BigDecimal.ONE,z.real()),
                BigDecimal.ONE.subtract(z.real()).abs().
                        compareTo(deltaTolerance)<0);
        assertTrue(String.format("Multiplicative Inverse fails: " +
                "expected imaginary=%1$gg, actual imaginary=%2$g",
                BigDecimal.ZERO,z.imaginary()),
                z.imaginary().abs().
                        compareTo(deltaTolerance)<0);
    }
}
